#include "global.h"
#include "Solver.h"
#include <boost/mpi.hpp>

//#define DEBUG

Solver::Solver()
  : pStates()
  , pState()
  , pWorld()
  , pTimer()
  , pElapsed(0)
  , pCounter(0)
  , pFails(0)
  , pNeighbour((pWorld.rank() + 1) % pWorld.size())
  , pWorkSent(false)
{
}

State Solver::getSolution() {
  return pState;
}

double Solver::getElapsed() {
  return pElapsed;
}

long Solver::getCount() {
  return pCounter;
}

inline void Solver::incNeighbour() {
  pNeighbour = (pNeighbour + 1) % pWorld.size();
  if (pNeighbour != pWorld.rank()) return;
  pNeighbour = (pNeighbour + 1) % pWorld.size();
}

int Solver::processMessages() {
  mpi::status kMsg;
  if (pStates.empty()) { //blocking
    //cout << pWorld.rank() << "\tPROCESS MESSAGES - blocking ..." << endl
    //     << "\tSTATES: " << pStates.size() << endl;
    kMsg = pWorld.probe();
  } else { //non-blocking
    //cout << pWorld.rank() << "\tPROCESS  MESSAGES - non-blocking" << endl
    //     <<  "\tSTATES: " << pStates.size() << endl;
    boost::optional<mpi::status> kOptional = pWorld.iprobe();   //iprobe
    if (!kOptional) return MSG_NONE;                            //optional value not set => return
    kMsg = *kOptional;                                          //optional value is set, set msg
  }

  switch (kMsg.tag()) {
    /* WORK */
    case MSG_WORK_REQUEST:
      //cout << pWorld.rank() << " << " << kMsg.source() << "\tMSG_WORK_REQUEST" << endl;
      //dostal jsem žádost o práci, tak ji přijmu
      pWorld.recv(kMsg.source(), kMsg.tag());
      //jestliže mám málo práce, odešlu o tom zprávu
      if (pStates.size() < (unsigned) (pWorld.size())) {
        //cout << pWorld.rank() << " >> " << kMsg.source() << "\tMSG_WORK_NOWORK " << endl;
        pWorld.send(kMsg.source(), MSG_WORK_NOWORK);
      //jestliže jí mám dost, rozdělím zásobník a odešlu ho
      } else {
        //cout << pWorld.rank() << " >> " << kMsg.source() << "\tMSG_WORK_SEND " << endl;
        pWorld.send(kMsg.source(), MSG_WORK_SEND, pStates.split());
        pWorkSent = true;
      }
      break;
    case MSG_WORK_SEND:
      if (pStates.size() > 0) throw "Stack should be empty!";
      //cout << pWorld.rank() << " << " << kMsg.source() << "\tMSG_WORK_SEND " << endl
      //     << "\tSTATES: " << pStates.size() << endl;
      //dostal jsem práci, tak je přijmu a resetuju nastavení failů
      pWorld.recv(kMsg.source(), kMsg.tag(), pStates);
      //cout << pWorld.rank() << " << " << kMsg.source() << "\t"
      //     << pStates.front() << endl;
      pFails = 0;
      break;
    case MSG_WORK_NOWORK:
      //cout << pWorld.rank() << " << " << kMsg.source()  << "\tMSG_WORK_NOWORK" << endl
      //     << "\tFAILS: " << pFails << endl;
      //dostal jsem zprávu o tom, že práci nedostanu, tak ji přijmu
      pWorld.recv(kMsg.source(), kMsg.tag());
      //jestliže mi failnula už polovina velikosti celé skupiny dotazů o práci a jsem root, tak pošlu peška
      if (pWorld.rank() == 0 && pFails/2 >= pWorld.size()) {
      //  cout << pWorld.rank() << " >> " << ((pWorld.rank() + 1) % pWorld.size()) << "\tMSG_TOKEN_WHITE" << endl;
        pWorld.send((pWorld.rank() + 1) % pWorld.size(), MSG_TOKEN_WHITE);
      //v jiném případě posunu souseda a zvýším fail counter
      } else {
        ++pFails;
      //  cout << pWorld.rank() << " >> " << pNeighbour << "\tMSG_WORK_REQUEST" << endl;
        pWorld.send(pNeighbour, MSG_WORK_REQUEST);
      }
      incNeighbour();
      break;

    /* TOKENS */
    case MSG_TOKEN_WHITE:
      cout << pWorld.rank() << " << " << kMsg.source() << "\tMSG_TOKEN_WHITE" << endl;
      //dostal jsem bílého peška, tak ho přijmu
      pWorld.recv(kMsg.source(), kMsg.tag());
      //v případě, že jsem root, tak to znamená, že se mi vrátil a ukončujeme výpočet
      if (pWorld.rank() == 0) throw EX_WORK_FINISHED; 
      //v případě, že root nejsem
      else {
        //pokud nemám stavy a nikomu sem práci od minule neposlal, tak pošlu dál bílého peška
        if (pStates.size() == 0 && pWorkSent == false) {
          cout << pWorld.rank() << " >> " << ((pWorld.rank() + 1) % pWorld.size()) << "\tMSG_TOKEN_WHITE" << endl;     
          pWorld.send((pWorld.rank() + 1) % pWorld.size(), MSG_TOKEN_WHITE);
        //pokud nějaké stavy mám nebo jsem někomu práci posílal, tak pošlu dál černého peška
        } else {
          cout << pWorld.rank() << " >> " << ((pWorld.rank() + 1) % pWorld.size()) << "\tMSG_TOKEN_BLACK" << endl;         
          pWorld.send((pWorld.rank() + 1) % pWorld.size(), MSG_TOKEN_BLACK);
        }
        pWorkSent = false;
      }
      break;
    case MSG_TOKEN_BLACK:
      cout << pWorld.rank() << " << " << kMsg.source() << "\tMSG_TOKEN_BLACK" << endl;
      //dostal jsem černého peška, tak ho přijmu
      pWorld.recv(kMsg.source(), kMsg.tag());
      //jsem root, tak si resetuju faily
      if (pWorld.rank() == 0) {
        cout << pWorld.rank() << " >> " << pNeighbour << "\tMSG_WORK_REQUEST" << endl;
        pWorld.send(pNeighbour, MSG_WORK_REQUEST);
        pFails = 0;
      //nejsem root, tak pošlu dál černého peška
      } else {
        cout << pWorld.rank() << " >> " << ((pWorld.rank() + 1) % pWorld.size()) << "\tMSG_TOKEN_BLACK" << endl;
        pWorld.send((pWorld.rank() + 1) % pWorld.size(), MSG_TOKEN_BLACK);
        pWorkSent = false;
      }
      break;

    /* SLAVE ONLY */
    case MSG_TERMINATE:
      if (pWorld.rank() == 0) throw EX_ERROR;
      cout << pWorld.rank() << " << " << kMsg.source() << "\tMSG_TERMINATE" << endl;
      //dostal jsem informaci o ukončení, tak ho přijmu a vyhodím ukončovací vyjímku EX_TERMINAE
      pWorld.recv(kMsg.source(), kMsg.tag());
      throw EX_TERMINATE;

    /* ROOT ONLY */
    case MSG_SOLUTION:
      if (pWorld.rank() != 0) throw EX_ERROR;
      cout << pWorld.rank() << " << " << kMsg.source() << "\tMSG_SOLUTION" << endl;
      //přimu řešení a vyhodím EX_SOLUTION vyjímku
      pWorld.recv(kMsg.source(), kMsg.tag(), pState);
      throw EX_SOLUTION;

    /* DEFAULT */
    default:
      //sem by se to teda dostat nemělo
      throw EX_ERROR;
  }
  //vrátím tag zprávy
  return kMsg.tag();
}

//return true if solution was found
inline void Solver::expand() {
   pState = pStates.back(); pStates.pop_back();

   //jestliže najdu solution, tak vyhodím vyjímku EX_SOLUTION
   if (pState.isFinal()) throw EX_SOLUTION;

   for(uint16_t i = 1;i <= pState.getMaxColor() && i <= pState.lastColor() + 1;++i) {
     if (!pState.willBeValid(i)) continue;
     ++pCounter;

     State kState = State(pState);
     if ((kState.nextNode() = i) == pState.lastColor() + 1) ++kState.lastColor();

     pStates.push_back(kState);

     if ((pCounter % 1000000) == 0) cout << pWorld.rank() << "\t" << kState;
   }
}

inline void Solver::innerLoop() {
  while (true) {
    while (!pStates.empty()) {
      if ((pCounter % CHECK_MSG_AMOUNT) == 0) processMessages();
      expand();
    }

    cout << pWorld.rank() << " >> " << pNeighbour << "\tMSG_WORK_REQUEST in INNERLOOP" << endl;
    pWorld.send(pNeighbour, MSG_WORK_REQUEST);
    while (processMessages() != MSG_WORK_SEND);
  }
}

void Solver::solve() {
  broadcast(pWorld, gInstance, 0);
  pTimer.restart();

  try {
    if (pWorld.rank() == 0) {                                                //root proces
      int kColors = 2;
      while (true) {
        cout << "START\t" << kColors << endl;
        //vložení prvního prázdného stavu
        State kState(kColors++); //kState.nextNode() = 1;
        pStates.push_back(kState);

        //končí, když je pStates prázdný nebo je pStates.size() > kInitialStates
        while (!pStates.empty() && pStates.size() < (unsigned)pWorld.size()) {
          expand();
        }
        //pWorld.send(1, MSG_WORK_SEND, pStates.split());
        //break;

        //vyřešeno, continue a přidání barvy, nikam nic neposíláme
        if (pStates.empty()) continue;
        cout << "INITIAL\t" << pStates.size() << endl;

        /*for (int i=0;i<pWorld.size();++i) {
          if (i != 0) pWorld.send(i, MSG_WORK_SEND, pStates.split());
        }*/

        try { //until solution or ADUV
          innerLoop();
        } catch (int aEx) {
          if (aEx != EX_WORK_FINISHED) throw aEx;
        }
      }
    } else { //until solution is found
      try {
        innerLoop();
      } catch (int aEx) {
        if (aEx == EX_SOLUTION) {
          cout << pWorld.rank() << "\tFOUND SOULUTION" << endl << "\t"
               << pState << endl;
          pWorld.send(0, MSG_SOLUTION, pState);
        }
      }
    }
  } catch (int aEx) {
    switch (aEx) {
      case EX_SOLUTION:
        //tohle by se mělo stát jedině rootu
        if (pWorld.rank() != 0) throw EX_ERROR;
        for (int i=0;i<pWorld.size();++i) {
          if (i != 0) {
            cout << pWorld.rank() << " >> " << i << "\tMSG_TERMINATE" << endl;
            pWorld.send(i, MSG_TERMINATE);
          }
        }
        break;
      case EX_TERMINATE:
        break;
      case EX_ERROR:
      default:
        throw "Unexpected program flow.";
    }
  }

  broadcast(pWorld, pState, 0);
  pElapsed = pTimer.elapsed();
}

// vim: ts=2 fdm=marker syntax=cpp expandtab sw=2
